#include <drivers/uart.h>
#include <kernel/initcall.h>
#include <stm32h7.h>
#include <drivers/irq.h>
#include <drivers/gpio.h>
#include <drivers/clock.h>
#include <kernel/syslog.h>
#include <drivers/platform.h>
#include <kernel/fdt.h>
#include <io.h>


#define UART_ISR_OFFSET                 (0x1C)
#define UART_ICR_OFFSET                 (0x20)
#define UART_RDR_OFFSET                 (0x24)
#define UART_TDR_OFFSET                 (0x28)
#define UART_BRR_OFFSET                 (0x0C)
#define UART_CR1_OFFSET                 (0x00)
#define UART_CR2_OFFSET                 (0x04)
#define UART_CR3_OFFSET                 (0x08)
#define UART_GTPR_OFFSET                (0x10)


static void stm32h7_uart_init(struct uart_master *master, uint32_t baudrate)
{
    uint32_t base = master->io_base;
    uint32_t clock_freq = master->clock_freq_hz;
    size_t cr1_val = 0;

    uint32_t integer = clock_freq / (baudrate * 16);
    //uint32_t decimal = 1; //maybe it's better?
    uint32_t decimal = (((clock_freq / 16) - baudrate * integer)*16 / baudrate);

    cr1_val |= (1 << 3) | (1 << 2) | (1 << 5); /* 清零CR1寄存器 */ /* 串口发送使能 */ /* 串口接收使能 */  /* 接收缓冲区非空中断使能 */
    write32(base + UART_CR1_OFFSET, cr1_val);

    write32(base + UART_BRR_OFFSET, (integer << 4) | (decimal));

    cr1_val |= (1 << 0); /* 串口使能 */
    write32(base + UART_CR1_OFFSET, cr1_val);
}


static void stm32h7_uart_start(struct uart_device *pdev)
{

}


static void stm32h7_uart_stop(struct uart_device *pdev)
{

}


static void stm32h7_uart_put_char(struct uart_device *dev, char ch)
{
    size_t base = dev->master->io_base;

    while(! (read32(base + UART_ISR_OFFSET) & (1 << 6)));
    write32(base + UART_TDR_OFFSET, ch);
}


static void stm32h7_uart_put_string(struct uart_device *dev, char *str)
{
    size_t base = dev->master->io_base;

    while(*str) {
        write32(base + UART_TDR_OFFSET, *str);
        while(! (read32(base + UART_ISR_OFFSET) & (1 << 6)));
        str++;
    }
}


static char stm32h7_uart_get_char(struct uart_device *dev)
{
    size_t base = dev->master->io_base;
    //io_setbit32(base + UART_ICR_OFFSET, 3); /* 清除溢出错误, 否则可能会卡死在串口中断服务函数里面 */
    
    while(read32(base + UART_ISR_OFFSET) & (1 << 5))
	    return read32(base + UART_RDR_OFFSET) & 0xFF;
}


static void stm32h7_uart_set_baudrate(struct uart_device *dev, uint32_t baudrate)
{
    size_t base = dev->master->io_base;
    size_t clock_freq = dev->master->clock_freq_hz;
    size_t cr1_val = read32(base + UART_CR1_OFFSET);

    cr1_val &= ~(1 << 0);
    write32(base + UART_CR1_OFFSET, cr1_val);  /* 关闭串口 */

    uint32_t integer = clock_freq / baudrate;
    //uint32_t decimal = 1; //maybe it's better?
    uint32_t decimal = ((clock_freq - baudrate * integer)*16 / baudrate);
    
    write32(base + UART_BRR_OFFSET, (integer << 4) | (decimal));

    cr1_val |= (1 << 0);
    write32(base + UART_CR1_OFFSET, cr1_val);  /* 串口使能 */
}


static struct uart_ops stm32h7_uart_ops = {
    .start = stm32h7_uart_start,
    .stop = stm32h7_uart_stop,
    .put_char = stm32h7_uart_put_char,
    .put_string = stm32h7_uart_put_string,
    .get_char = stm32h7_uart_get_char,
    .set_baudrate = stm32h7_uart_set_baudrate,
};


static int stm32h7_uart_probe(struct platform_device *dev)
{
    fdt_node_t *uart_node = dev->device.dt_node;

    const char *uart_clock = fdt_read_prop_string(uart_node, "clock-bus");
    if(uart_clock == NULL) {
        return -1;
    }

    size_t clock_bit = -1;
    if(fdt_read_prop_int(uart_node, "clock-bit", &clock_bit)) {
        PLATFORM_SYSLOG_ERROR("uart: no clock-bit property");
        return -1;
    }

    clock_enable_by_name(uart_clock, clock_bit);

    size_t irq_num = 0;
    if(fdt_read_prop_int(uart_node, "interrupts", &irq_num)) {
        PLATFORM_SYSLOG_ERROR("uart: no interrupts property");
    }

    size_t baudrate = 0;
    if(fdt_read_prop_int(uart_node, "baudrate", &baudrate)) {
        PLATFORM_SYSLOG_ERROR("uart: no baudrate property");
        return -1;
    }

    struct uart_gpio pins = {-1, -1};
    if(uart_master_get_gpio_from_fdt(uart_node, &pins)) {
        return -1;
    }

    struct uart_master *master = kmalloc(sizeof(struct uart_master));
    if(master == NULL) {
        PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
        return -1;
    }

    master->ops = &stm32h7_uart_ops;
    master->pins = pins;
    master->io_base = dev->io_base;
    master->clock_freq_hz = clock_get_freq_by_name(uart_clock);

    platform_set_drvdata(dev, master);

    stm32h7_uart_init(master, baudrate);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(uart_node, child) {
        const char *comp = fdt_read_prop_string(child, "compatible");
        if(comp == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: no compatible property");
            continue;
        }

        const char *status = fdt_read_prop_string(child, "status");
        if(status != NULL && strcmp(status, "disabled") == 0) {
            PLATFORM_SYSLOG_INFO("uart: status is disabled");
            continue;
        }

        struct uart_device *udev = kmalloc(sizeof(struct uart_device));
        if(udev == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
            return -1;
        }

        udev->device.name = uart_node->name;
        udev->device.compatible = comp;
        udev->device.driver = NULL;
        udev->master = master;

        if(uart_device_register(udev) < 0) {
            PLATFORM_SYSLOG_ERROR("uart: uart_device_register failed");
            kfree(udev);
            return -1;
        }

        if(irq_num) {
            udev->irq_num = irq_num;
        }
    }

    return 0;
}


static void stm32h7_uart_remove(struct platform_device *dev)
{
    
}


static struct of_device_id stm32h7_uart_match[] = {
    {.compatible = "stm32h7-uart", },
    { }
};


static struct platform_driver stm32h7_uart_driver = {
    .driver = {
        .name = "stm32h7-uart",
        .of_match_table = stm32h7_uart_match,
    },
    .probe = stm32h7_uart_probe,
    .remove = stm32h7_uart_remove,
};


static int stm32h7_uart_driver_register(void)
{
    return platform_driver_register(&stm32h7_uart_driver);
}


core_initcall(stm32h7_uart_driver_register);
